package hu.pixelesztergalyos.continuity;

import java.awt.Point;
import java.awt.geom.Point2D;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

import org.w3c.dom.NodeList;

//Mosaic class stores the MosaicElements and the information about the whereabouts of the key,
//character and gate and additionally stores TransitionBlocks for the detection of the edges of the Mosaics
public class Mosaic {
	Point coord = new Point();                // coordinates in the mosaics array of Level
	public ArrayList<MosaicElement> elements; // the list for the MosaicElements
	public Level owner;                       // the owner of the mosaics for communication purposes
	private boolean hasKey;                   // stores that the mosaic has the key in it or not
	private boolean hasGate;                  // stores that the mosaic has the gate in it or not
	private boolean hasCharacter;             // stores if the mosaic has the 1st character
	private boolean hasCharacter2;            // stores if the mosaic has the 2nd character 
	private TransitionBlock tbLeft;           // transitionBlock storing the left side of the mosaic
	private TransitionBlock tbRight;          // transitionBlock storing the right side of the mosaic
	private TransitionBlock tbBottom;         // transitionBlock storing the bottom of the mosaic
	private TransitionBlock tbTop;            // transitionBlock storing the top of the mosaic

  public static final float TB_WIDTH = 1.f; // TB_WIDTH: graphical float size of the transitionBlock

	public char[] rightSide;                  // rightmost elements within the mosaic (15)
	public char[] leftSide;                   // leftmost elements within the mosaic (15)
	public char[] bottomSide;                 // bottom elements within the mosaic (26)
	public char[] topSide;                    // uppermost elements within the mosaic (26)

	// Constructor
	public Mosaic(String[] rowArray, Level owner, Point coord) {
		this.owner = owner;
		this.coord = coord;
		elements = new ArrayList<MosaicElement>();
		leftSide = new char[15];
		rightSide = new char[15];
		bottomSide = new char[26];
		topSide = new char[26];
		for (int rowNumber = 0; rowNumber < 15; rowNumber++) { // for():
																//  Vegigmegyunk a  megtalalt "row" elemeken
			for (int colNumber = 0; colNumber < 26; colNumber++) { // for():
																	// Vegigmegyunk  a stringen
				if (rowNumber == 0)
					topSide[colNumber] = rowArray[rowNumber].charAt(colNumber);  // fill  topSide﻿ with﻿ first﻿ row
				if (rowNumber == 15 - 1)
					bottomSide[colNumber] = rowArray[rowNumber]
							.charAt(colNumber);                                      // fill bottomSide with last row
				if (colNumber == 0)
					leftSide[rowNumber] = rowArray[rowNumber].charAt(colNumber); // fill leftSide with first of every row
				if (colNumber == 26 - 1)
					rightSide[rowNumber] = rowArray[rowNumber]
							.charAt(colNumber);                                      // fill rightSide with last of every row
				switch (rowArray[rowNumber].charAt(colNumber)) {               // switch(): Az adott blokkertek alapjan hozunk letre egy MosaicElementet
				case 'x': // 'x': fal elem - BuildingBlock
					elements.add(new BuildingBlock((float) (colNumber * 10),
							(float) (rowNumber * 10)));
					break;
				case 'f': // 'f': átlós fal elem - BuildingBlock
					elements.add(new BuildingBlock((float) (colNumber * 10),
							(float) (rowNumber * 10)));
					break;
				case 'g': // 'g': átlós fal elem - BuildingBlock
					elements.add(new BuildingBlock((float) (colNumber * 10),
							(float) (rowNumber * 10)));
					break;
				case 'h': // 'h': átlós fal elem - BuildingBlock
					elements.add(new BuildingBlock((float) (colNumber * 10),
							(float) (rowNumber * 10)));
					break;
				case 'j': // 'j': átlós fal elem - BuildingBlock
					elements.add(new BuildingBlock((float) (colNumber * 10),
							(float) (rowNumber * 10)));
					break;
				case 'k': // 'k': kulcs elem - Key
					owner.setKey(new Key((float) (colNumber * 10),
							(float) (rowNumber * 10)));
					setHasKey(true);
					break;
				case 'l': // 'l': kapu elem - Gate
					owner.setGate(new Gate((float) (colNumber * 10),
							(float) (rowNumber * 10)));
					setHasGate(true);
					break;
				case 'p': // 'p': karakter elem - Character
					owner.setCharacter(1, new Character(new CharacterControls(
							(byte) 37, (byte) 38, (byte) 39),
							(float) (colNumber * 10), (float) (rowNumber * 10),
							owner, 1));
					owner.setCurrentMosaic(new Point(coord.x, coord.y));
					setHasCharacter(true);
					break;
				case 'q': // 'q': karakter elem - Character
					owner.setCharacter(2, new Character(new CharacterControls(
							(byte) 65, (byte) 87, (byte) 68),
							(float) (colNumber * 10), (float) (rowNumber * 10),
							owner, 2));
					setHasCharacter2(true);
					break;
				} // END[SWITCH]
			} // END[for(COL)]
		} // END[for(ROW)]

		tbLeft = new TransitionBlock(TransitionType.LEFT); 
		tbLeft.setBoundingBox(TB_WIDTH, Level.MOSAIC_HEIGHT);
		tbLeft.setOrigin(new Point2D.Float(0.f, 0.f));

		tbRight = new TransitionBlock(TransitionType.RIGHT);
		tbRight.setBoundingBox(TB_WIDTH, Level.MOSAIC_HEIGHT);
		tbRight.setOrigin(new Point2D.Float(Level.MOSAIC_WIDTH + 8, 0.f));

		tbBottom = new TransitionBlock(TransitionType.BOTTOM);
		tbBottom.setBoundingBox(Level.MOSAIC_WIDTH, TB_WIDTH);
		tbBottom.setOrigin(new Point2D.Float(0.f, Level.MOSAIC_HEIGHT - TB_WIDTH));

		tbTop = new TransitionBlock(TransitionType.TOP);
		tbTop.setBoundingBox(Level.MOSAIC_WIDTH, TB_WIDTH);
		tbTop.setOrigin(new Point2D.Float(0.f, 0.f));
	}

	/**
	 * 
	 * @return
	 */
	public boolean getHasCharacter() {
		return hasCharacter;                 // retrieves whether the mosaic containes the 1st character
	}

	public boolean getHasCharacter2() {
		return hasCharacter2;                 // retrieves whether the mosaic containes the 2nd character
	}

	/**
	 * 
	 * @return
	 */
	public boolean getHasGate() {
		return hasGate;                       // retrieves whether the mosaic containes the gate
	}

	/**
	 * 
	 * @return
	 */
	public boolean getHasKey() {
		return hasKey;                       // retrieves whether the mosaic containes the key
	}

	/**
	 * 
	 * @return
	 */
	// sets a flag for the mosaic to keep track of the location of the characters
	public void setHasCharacter(boolean hasCharacter) {
		this.hasCharacter = hasCharacter;
	}
	public void setHasCharacter2(boolean hasCharacter) {
		this.hasCharacter2 = hasCharacter;
	}

	/**
	 * 
	 * @return
	 */
	// sets a flag for the mosaic to keep track of the location of the gate
	public void setHasGate(boolean hasGate) {
		this.hasGate = hasGate;
	}

	/**
	 * 
	 * @return
	 */
	// sets a flag for the mosaic to keep track of the location of the key
	public void setHasKey(boolean hasKey) {
		this.hasKey = hasKey;
	}

	/**
	 * 
	 * @param ((MosaicElement)collideable)
	 * @return
	 * @throws IOException
	 */

	public boolean collisionDetectionBlock(Collideable collideable,
			Collideable toCall) throws IOException {
		Iterator<MosaicElement> itr = elements.iterator();

		while (itr.hasNext()) {
			MosaicElement element = itr.next();
			if ((MosaicElement) collideable != element) // Don't compare it with itself
			{
				if (element instanceof BuildingBlock) {
					if (element.getBoundingBox().intersects(
							((MosaicElement) collideable).getBoundingBox())) {

						return false; // Invalid move
					}
				}
			}
		}
		// GATE
		if (((MosaicElement) collideable).getBoundingBox().intersects(
				owner.getGate().getBoundingBox()) &&(owner.getCurrentMosaic().hasGate)) {
			owner.getGate().hitBy((Character) toCall);
			return true;
		}
		// KEY
		if (((MosaicElement) collideable).getBoundingBox().intersects(
				owner.getKey().getBoundingBox())&&(owner.getCurrentMosaic().hasKey)) {
			owner.getKey().hitBy((Character) toCall);
			return true;
		}
		
		// TRANSITIONBLOCKS
				if (owner.getCurrentMosaic().tbBottom.getBoundingBox().intersects(
						((MosaicElement) collideable).getBoundingBox())) {
					owner.getCurrentMosaic().tbBottom.hitBy((Character) toCall);
					return false;
				}

				if (owner.getCurrentMosaic().tbLeft.getBoundingBox().intersects(
						((MosaicElement) collideable).getBoundingBox())) {
					owner.getCurrentMosaic().tbLeft.hitBy((Character) toCall);
					return false;
				}

				if (owner.getCurrentMosaic().tbTop.getBoundingBox().intersects(
						((MosaicElement) collideable).getBoundingBox())) {
					owner.getCurrentMosaic().tbTop.hitBy((Character) toCall);
					return false;
				}

				if (owner.getCurrentMosaic().tbRight.getBoundingBox().intersects(
						((MosaicElement) collideable).getBoundingBox())) {
					owner.getCurrentMosaic().tbRight.hitBy((Character) toCall);
					return false;
				}		
		// OTHER CHARACTER
		if (((Character) collideable).number != owner.getCharacter(1).number) // Character
																				// #2
		// vs. Character
		// #1
		{
			if (((Character) collideable).getBoundingBox().intersects(
					owner.getCharacter(1).getBoundingBox())) // Collision
			{
				owner.getCharacter(1).hitBy((Character) toCall);
				return true;
			}
		} else {
			if (((Character) collideable).getBoundingBox().intersects(
					owner.getCharacter(2).getBoundingBox())) // Collision
			{
				owner.getCharacter(2).hitBy((Character) toCall);
				return true;
			}
		}
		return true;		
	}

	// Collision Detection method to check if the given objects are colliding or not
	public boolean collisionDetection(Collideable collideable,
			Collideable toCall) throws IOException {
		Iterator<MosaicElement> itr = elements.iterator();

		while (itr.hasNext()) {
			MosaicElement element = itr.next();
			if ((MosaicElement) collideable != element) // Don't compare it with itself
			{
				if (element.getBoundingBox().intersects(
						((MosaicElement) collideable).getBoundingBox())) {
					if (element instanceof BuildingBlock)
						((BuildingBlock) element).hitBy((Character) toCall);
					return false; // Invalid move
				}
			}
		}

		// TRANSITIONBLOCKS
		if (owner.getCurrentMosaic().tbBottom.getBoundingBox().intersects(
				((MosaicElement) collideable).getBoundingBox())) {
			owner.getCurrentMosaic().tbBottom.hitBy((Character) toCall);
			return false;
		}

		if (owner.getCurrentMosaic().tbLeft.getBoundingBox().intersects(
				((MosaicElement) collideable).getBoundingBox())) {
			owner.getCurrentMosaic().tbLeft.hitBy((Character) toCall);
			return false;
		}

		if (owner.getCurrentMosaic().tbTop.getBoundingBox().intersects(
				((MosaicElement) collideable).getBoundingBox())) {
			owner.getCurrentMosaic().tbTop.hitBy((Character) toCall);
			return false;
		}

		if (owner.getCurrentMosaic().tbRight.getBoundingBox().intersects(
				((MosaicElement) collideable).getBoundingBox())) {
			owner.getCurrentMosaic().tbRight.hitBy((Character) toCall);
			return false;
		}

		// GATE
		if (((MosaicElement) collideable).getBoundingBox().intersects(
				owner.getGate().getBoundingBox()) &&(owner.getCurrentMosaic().hasGate)) {
			owner.getGate().hitBy((Character) toCall);
			return true;
		}
		// KEY
		if (((MosaicElement) collideable).getBoundingBox().intersects(
				owner.getKey().getBoundingBox())&&(owner.getCurrentMosaic().hasKey)) {
			owner.getKey().hitBy((Character) toCall);
			return true;
		}
		// OTHER CHARACTER
		if (((Character) collideable).number != owner.getCharacter(1).number) // Character
																				// #2
		// vs. Character
		// #1
		{
			if (((Character) collideable).getBoundingBox().intersects(
					owner.getCharacter(1).getBoundingBox())) // Collision
			{
				owner.getCharacter(1).hitBy((Character) toCall);
				return true;
			}
		} else {
			if (((Character) collideable).getBoundingBox().intersects(
					owner.getCharacter(2).getBoundingBox())) // Collision
			{
				owner.getCharacter(2).hitBy((Character) toCall);
				return true;
			}
		}

		return true;
	}

	// Return elements for proto writeout
	public void writeLevel(String filename, BufferedWriter out)
			throws IOException {
		int elementsSize = elements.size();
		out.write("<mosaic row=\"" + (coord.x + 1) + "\" col=\""
				+ (coord.y + 1) + "\" >");
		/*
		 * String outString = ""; int lastX = 0; int lastY = 0;
		 */
		char temp_map[][] = new char[15][26];
		for (int i = 0; i < 15; i++)
			for (int j = 0; j < 26; j++) {
				temp_map[i][j] = ':';
			}

		for (int i = 0; i < elementsSize; i++) {
			Point2D.Float elementPos = (Point2D.Float) elements.get(i)
					.getOrigin().clone();
			elementPos.x = ((int) Math.round(elementPos.x / 10));
			elementPos.y = ((int) Math.round(elementPos.y / 10));
			temp_map[(int) elementPos.y][(int) elementPos.x] = 'x';
		 }

		if (this.hasCharacter) {
			temp_map[((int) Math
					.round(owner.getCharacter(1).getOrigin().y / 10))][((int) Math
					.round(owner.getCharacter(1).getOrigin().x / 10))] = 'p';

		}
		if (this.hasCharacter2) {
			temp_map[((int) Math
					.round(owner.getCharacter(2).getOrigin().y / 10))][((int) Math
					.round(owner.getCharacter(2).getOrigin().x / 10))] = 'q';

		}
		if (this.hasGate) {
			temp_map[((int) Math.round(owner.getGate().getOrigin().y / 10))][((int) Math
					.round(owner.getGate().getOrigin().x / 10))] = 'l';
		}
		if (this.hasKey) {
			temp_map[((int) Math.round(owner.getKey().getOrigin().y / 10))][((int) Math
					.round(owner.getKey().getOrigin().x / 10))] = 'k';
		}

		for (int i = 0; i < 15; i++) {
			out.write("<row>");
			for (int j = 0; j < 26; j++) {
				out.write(temp_map[i][j]);
			}
			out.write("</row>");
		}
		out.write("</mosaic>");
	}
}
